/*
 * Copyright 2016 Alibaba Group Holding Limited
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA  02111-1307  USA
 */
#include <stdio.h>
#include <string>
#include <gtest/gtest.h>
#include <properties.h>
#include <time.h>
//#include <log/Log.h>
#define LOG_TAG "libproperties.test"

using namespace std;

#define  LOG(format, args...) printf(format"\n", ##args)
#define OK(result) EXPECT_EQ(0, result)

void propertyChanged1(const char *key, void *data) {
    char *buf = (char *)data;
    int result = property_get(key, buf, NULL);
    assert(result >= 0);
}
void propertyChanged2(const char *key, void *data) {
    propertyChanged1(key, data);
}
void propertyChangedDelay(const char *key, void *data) {
    propertyChanged1(key, data);
    sleep(1);
}
void propertyChangedUnwatch(const char *key, void *data) {
    propertyChanged1(key, data);
    int result = property_unwatch(key, NULL, data);
    assert(result == 0);
}

void clearProperty(const char *key) {
    int result = property_set(key, "");
    assert(result == 0);
}

TEST(PropertiesWatchTest, UnwatchAll) {
    // setup
    const char *key = "test.key.unwatch.all";
    clearProperty(key);
    // given
    char output1[PROP_VALUE_MAX] = {0};
    OK(property_watch(key, propertyChanged1, output1));
    // when
    void property_unwatch_all();
    property_unwatch_all();
    // then
    const char *value = "value";
    OK(property_set(key, value));
    sleep(1);
    EXPECT_STREQ("", output1);
    EXPECT_EQ(property_unwatch(key, propertyChanged1, output1), -1);
}

void forkAndExitChildProcess() {
    int pid = fork();
    if (pid == 0) {
        //LOG_I("child process exit.");
        exit(0);
    } else {
        //LOG_I("waiting for child process exit.");
        wait(NULL);
    }
}
TEST(PropertiesWatchTest, WatchForkWhenChildProcessExits) {
    char key[PROP_NAME_MAX];
    sprintf(key, "test.key.fork.%d", (int)time(NULL));

    char output[PROP_VALUE_MAX] = {0};
    OK(property_watch(key, propertyChanged1, output));

    forkAndExitChildProcess();

    const char *value = "test.value";
    OK(property_set(key, value));
    sleep(1);
    EXPECT_STREQ(value, output);

    OK(property_unwatch(key, propertyChanged1, output));
}
TEST(PropertiesWatchTest, WatchForkInChildProcess) {
    char key[PROP_NAME_MAX];
    sprintf(key, "test.key.fork.%d", (int)time(NULL));

    char output[PROP_VALUE_MAX] = {0};
    OK(property_watch(key, propertyChanged1, output));

    int pid = fork();
    if (pid == 0) {
        //LOG_I("running tests in child process.");
        OK(property_watch(key, propertyChanged1, output));

        const char *value = "test.value";
        OK(property_set(key, value));
        sleep(1);

        bool result = strcmp(value, output) == 0;
        exit(result ? 0 : 1);
    } else {
        //LOG_I("wait tests in child process.");
        int status = 0;
        int child = waitpid(pid, &status, 0);
        EXPECT_EQ(child, pid);
        EXPECT_TRUE(WIFEXITED(status));
        EXPECT_EQ(WEXITSTATUS(status), 0);
    }

    OK(property_unwatch(key, propertyChanged1, output));
}
TEST(PropertiesWatchTest, WatchNonExistingKeyOK) {
    char key[PROP_NAME_MAX];
    sprintf(key, "test.key.nonexisting.%d", (int)time(NULL));

    char output[PROP_VALUE_MAX] = {0};
    OK(property_watch(key, propertyChanged1, output));

    const char *value = "test.value";
    OK(property_set(key, value));
    sleep(1);
    EXPECT_STREQ(value, output);

    OK(property_unwatch(key, propertyChanged1, output));
}

TEST(PropertiesWatchTest, WatchExistingKeyOK) {
    const char *key = "test.key.existing";
    clearProperty(key);

    char output[PROP_VALUE_MAX] = {0};
    OK(property_watch(key, propertyChanged1, output));

    const char *value = "value";
    OK(property_set(key, value));
    sleep(1);
    EXPECT_STREQ(value, output);

    OK(property_unwatch(key, propertyChanged1, output));
}

TEST(PropertiesWatchTest, SetSameValueWontCallCallback) {
    const char *key = "test.key.wontcalled";
    const char *value = "value";
    OK(property_set(key, value));

    char output[PROP_VALUE_MAX] = {0};
    OK(property_watch(key, propertyChanged1, output));

    OK(property_set(key, value));
    sleep(1);
    EXPECT_STREQ("", output);

    OK(property_unwatch(key, propertyChanged1, output));
}

TEST(PropertiesWatchTest, WatchSameKeyDiffCallbackOK) {
    const char *key = "test.key.watch.ok";
    clearProperty(key);

    //LOG("first watch ok");
    char output1[PROP_VALUE_MAX] = {0};
    OK(property_watch(key, propertyChanged1, output1));

    const char *value0 = "value0";
    OK(property_set(key, value0));
    sleep(1);
    EXPECT_STREQ(value0, output1);


    //LOG("second watch ok");
    char output2[PROP_VALUE_MAX] = {0};
    OK(property_watch(key, propertyChanged2, output2));

    const char *value1 = "value1";
    OK(property_set(key, value1));
    sleep(1);
    EXPECT_STREQ(value1, output1);
    EXPECT_STREQ(value1, output2);

    OK(property_unwatch(key, propertyChanged1, output1));
    OK(property_unwatch(key, propertyChanged2, output2));
}

TEST(PropertiesWatchTest, WatchSameKeyCallbackDiffDataOK) {
    // setup
    const char *key = "test.key.watch.diffdata.ok";
    clearProperty(key);
    // given
    char output1[PROP_VALUE_MAX] = {0};
    char output2[PROP_VALUE_MAX] = {0};
    OK(property_watch(key, propertyChanged1, output1));
    OK(property_watch(key, propertyChanged1, output2));
    // when
    const char *value = "value";
    OK(property_set(key, value));
    sleep(1);
    // then
    EXPECT_STREQ(value, output1);
    EXPECT_STREQ(value, output2);

    // when
    OK(property_unwatch(key, propertyChanged1, output1));
    OK(property_unwatch(key, propertyChanged1, output2));
    // then
    OK(property_set(key, "value2"));
    sleep(1);
    EXPECT_STREQ(value, output1);
    EXPECT_STREQ(value, output2);
}

TEST(PropertiesWatchTest, UnwatchNullUserDataFail) {
    // setup
    const char *key = "test.key.unwatch.nulldata.fail";
    clearProperty(key);
    // given
    char output1[PROP_VALUE_MAX] = {0};
    OK(property_watch(key, propertyChanged1, output1));

    EXPECT_EQ(property_unwatch(key, propertyChanged1, NULL), -1);
    // when
    const char *value = "value";
    OK(property_set(key, value));
    sleep(1);
    // then
    EXPECT_STREQ(value, output1);
    OK(property_unwatch(key, propertyChanged1, output1));
}

TEST(PropertiesWatchTest, UnwatchOK) {
    const char *key = "test.key.unwatch";
    clearProperty(key);

    char output1[PROP_VALUE_MAX] = {0};
    char output2[PROP_VALUE_MAX] = {0};
    OK(property_watch(key, propertyChanged1, output1));
    OK(property_watch(key, propertyChanged2, output2));

    //LOG("remove first watch ok");
    OK(property_unwatch(key, propertyChanged1, output1));

    const char *value2 = "value2";
    OK(property_set(key, value2));
    sleep(1);
    EXPECT_STREQ("", output1);
    EXPECT_STREQ(value2, output2);


    //LOG("remove second watch ok");
    OK(property_unwatch(key, propertyChanged2, output2));

    OK(property_set(key, "value3"));
    sleep(1);
    EXPECT_STREQ("", output1);
    EXPECT_STREQ(value2, output2);
}

TEST(PropertiesWatchTest, RewatchSameKeyCallbackFail) {
    const char *key = "test.key.rewatch.fail";
    OK(property_watch(key, propertyChanged1, NULL));

    int result = property_watch(key, propertyChanged1, NULL);
    EXPECT_EQ(result, -1);

    OK(property_unwatch(key, propertyChanged1, NULL));
}

TEST(PropertiesWatchTest, UnwatchOnNonWatchThreadWillBlockWhenCallbackIsRunning) {
    const char *key = "test.key.unwatch";
    clearProperty(key);
    // given
    char output1[PROP_VALUE_MAX] = {0};
    char output2[PROP_VALUE_MAX] = {0};
    OK(property_watch(key, propertyChangedDelay, output1));
    OK(property_watch(key, propertyChanged2, output2));
    // when
    const char *value = "value";
    OK(property_set(key, value));
    sleep(1);
    OK(property_unwatch(key, propertyChanged2, output2));
    sleep(1);
    // then
    EXPECT_STREQ(value, output1);
    EXPECT_STREQ(value, output2);

    OK(property_unwatch(key, propertyChangedDelay, output1));
}

TEST(PropertiesWatchTest, UnwatchOnWatchThreadOK) {
    // setup
    const char *key = "test.key.unwatch";
    clearProperty(key);
    // given
    char output1[PROP_VALUE_MAX] = {0};
    OK(property_watch(key, propertyChangedUnwatch, output1));
    // when
    const char *value = "value";
    OK(property_set(key, value));
    sleep(1);
    // then
    EXPECT_STREQ(value, output1);
    EXPECT_EQ(property_unwatch(key, propertyChangedUnwatch, output1), -1);
}


TEST(PropertiesSetGetTest, Key32BytesFail) {
    const char *key = "12345678901234567890123456789012";
    const char *value = "test.value";
    int result = property_set(key, value);
    EXPECT_EQ(result, -1);
}

TEST(PropertiesSetGetTest, Key31BytesOK) {
    const char *key = "1234567890123456789012345678901";
    const char *value = "test.value";
    int result = property_set(key, value);
    EXPECT_EQ(result, 0);
    char actualValue[PROP_VALUE_MAX];
    result = property_get(key, actualValue, "");
    EXPECT_EQ(result, (int)strlen(actualValue));
    EXPECT_STREQ((const char *)actualValue, value);
}

TEST(PropertiesSetGetTest, Value91BytesOK) {
    const char *key = "test.key";
    const char *value = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901";
    int result = property_set(key, value);
    EXPECT_EQ(result, 0);
    char actualValue[PROP_VALUE_MAX];
    result = property_get(key, actualValue, "");
    EXPECT_EQ(result, (int)strlen(actualValue));
    EXPECT_STREQ(actualValue, value);
}

TEST(PropertiesSetGetTest, Value92BytesFail) {
    const char *key = "test.key";
    const char *value = "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012";
    int result = property_set(key, value);
    EXPECT_EQ(-1, result);
}


static void record_prop(const char* key, const char* name, void* opaque) {
    EXPECT_STREQ("test_property_list", (char *)opaque);
}

TEST(PropertiesListTest, Property_listOK) {
    const char *cookie = "test_property_list";
    OK(property_list(record_prop, (void *)cookie));
}
